#include <dspSimulator/instruction/mv.h>

bool MV::checkCondition(std::shared_ptr<DspMachine> dspMachine) {
    if (isCompactInstruction) {
        return true;
    } else {
        return checkStandardCondition(dspMachine);
    }
}

void MV::loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                           std::shared_ptr<llvm::IRBuilder<>> irBuilder) {
    llvm::FunctionType *funcType = llvm::FunctionType::get(irBuilder->getVoidTy(), false);
    // --------------------------------- MV (.unit) xsint, sint---------------------------------------------
    {
        llvm::Function *mvL = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "MVKind1",
                                                     module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", mvL);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2Reg = irBuilder->CreateLoad(src2RegPtr);

        irBuilder->CreateStore(src2Reg, dstRegPtr);
        irBuilder->CreateRetVoid();
    }

    // --------------------------------- MV (.unit) slong, slong--------------------------------------------
    {
        llvm::Function *mvL = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "MVKind2",
                                                     module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", mvL);
        irBuilder->SetInsertPoint(entryBB);

        auto dstLoRegPtr = getDstLoRegPtr(module, irBuilder);
        auto dstHiRegPtr = getDstHiRegPtr(module, irBuilder);

        auto src2LoRegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2HiRegPtr = getSrc2HiRegPtr(module, irBuilder);
        auto src2Lo = irBuilder->CreateLoad(src2LoRegPtr);
        auto src2Hi = irBuilder->CreateLoad(src2HiRegPtr);

        irBuilder->CreateStore(src2Lo, dstLoRegPtr);
        auto dstHiTrunc = irBuilder->CreateTrunc(src2Hi, irBuilder->getInt8Ty());
        auto dstHi = irBuilder->CreateSExt(dstHiTrunc, irBuilder->getInt32Ty());
        irBuilder->CreateStore(dstHi, dstHiRegPtr);
        irBuilder->CreateRetVoid();
    }
}

std::shared_ptr<Instruction> MV::decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) {
    std::shared_ptr<MV> mv;
    auto dspInstInfo = std::dynamic_pointer_cast<DspInstInfo>(instInfo);
    bool isStandardInst = dspInstInfo->isStandardInstruction();
    if (isStandardInst) {
        auto data = vectorToBitSet<32>(dspInstInfo->getBits());
        if (checkBits<10>(data, 2, 0b0000010110) && checkBits<6>(data, 12, 0b000000) &&
                existStandardCondition(data)) {
            // .L unit Opfield 0000010 Kind1
            mv = std::make_shared<MV>(dspInstInfo->getAddress(), baseCycle);
            mv->creg_z = extractBits<4>(data, 28);
            mv->dst = extractBits<5>(data, 23).to_ulong();
            mv->src2 = extractBits<5>(data, 18).to_ulong();
            mv->s = data[1];
            mv->p = data[0];
            if (mv->s == 0) {
                mv->funcUnit = FuncUnit::L1;
            } else {
                mv->funcUnit = FuncUnit::L2;
            }
            mv->instFormat = InstFormat::Kind1;
        } else if (checkBits<6>(data, 12, 0b000000) && checkBits<10>(data, 2, 0b0100000110) &&
                existStandardCondition(data)) {
            mv = std::make_shared<MV>(dspInstInfo->getAddress(), baseCycle);
            mv->creg_z = extractBits<4>(data, 28);
            mv->dst = extractBits<5>(data, 23).to_ulong();
            mv->src2 = extractBits<5>(data, 18).to_ulong();
            mv->s = data[1];
            mv->p = data[0];
            if (mv->s == 0) {
                mv->funcUnit = FuncUnit::L1;
            } else {
                mv->funcUnit = FuncUnit::L2;
            }
            mv->instFormat = InstFormat::Kind2;
        } else if (checkBits<11>(data, 2, 0b1111110110) && existStandardCondition(data)) {
            // .L Kind3
            mv = std::make_shared<MV>(dspInstInfo->getAddress(), baseCycle);
            mv->creg_z = extractBits<4>(data, 28);
            mv->dst = extractBits<5>(data, 23).to_ulong();
            mv->src2 = extractBits<5>(data, 18).to_ulong();
            mv->cst = extractBits<5>(data, 13).to_ulong();
            mv->x = data[12];
            mv->s = data[1];
            mv->p = data[0];
            if (mv->s == 0) {
                mv->funcUnit = FuncUnit::L1;
            } else {
                mv->funcUnit = FuncUnit::L2;
            }
            mv->instFormat = InstFormat::Kind1;
        } else if (checkBits<5>(data, 2, 0b00000) && checkBits<10>(data, 2, 0b0001101000) &&
                existStandardCondition(data)) {
            // S Kind1
            mv = std::make_shared<MV>(dspInstInfo->getAddress(), baseCycle);
            mv->creg_z = extractBits<4>(data, 28);
            mv->dst = extractBits<5>(data, 23).to_ulong();
            mv->src2 = extractBits<5>(data, 18).to_ulong();
            mv->x = data[12];
            mv->s = data[1];
            mv->p = data[0];
            if (mv->s == 0) {
                mv->funcUnit = FuncUnit::S1;
            } else {
                mv->funcUnit = FuncUnit::S2;
            }
            mv->instFormat = InstFormat::Kind1;
        } else if (checkBits<16>(data, 2, 0b0000001001010000) && existStandardCondition(data)) {
            mv = std::make_shared<MV>(dspInstInfo->getAddress(), baseCycle);
            mv->creg_z = extractBits<4>(data, 28);
            mv->dst = extractBits<5>(data, 23).to_ulong();
            mv->src2 = extractBits<5>(data, 18).to_ulong();
            mv->s = data[1];
            mv->p = data[0];
            if (mv->s == 0) {
                mv->funcUnit = FuncUnit::D1;
            } else {
                mv->funcUnit = FuncUnit::D2;
            }
            mv->instFormat = InstFormat::Kind1;
        }
    } else {
        auto data = vectorToBitSet<16>(dspInstInfo->getBits());
        if (checkBits<2>(data, 1, 0b11) && checkBits<2>(data, 5, 0b00)) {
            mv = std::make_shared<MV>(dspInstInfo->getAddress(), baseCycle);
            mv->isCompactInstruction = true;
            mv->compactInstKind = "LSDmvto";
            mv->s = data[0];
            auto unit = extractBits<2>(data, 3);
            if (unit == 0b00)
                if (mv->s == 0)
                    mv->funcUnit = FuncUnit::L1;
                else
                    mv->funcUnit = FuncUnit::L2;
            else if (unit == 0b01)
                if (mv->s == 0)
                    mv->funcUnit = FuncUnit::S1;
                else
                    mv->funcUnit = FuncUnit::S2;
            else if (unit == 0b10) {
                if (mv->s == 0)
                    mv->funcUnit = FuncUnit::D1;
                else {
                    mv->funcUnit = FuncUnit::D2;
                }
            }
            mv->dst = extractBits<3>(data, 13).to_ulong();
            mv->x = data[12];
            mv->src2 = extractBits<3>(data, 7).to_ulong();
            mv->src2 += extractBits<2>(data, 10).to_ulong() << 3;
            mv->PROT = dspInstInfo->getPROT();
            mv->RS = dspInstInfo->getRS();
            mv->BR = dspInstInfo->getBR();
            mv->SAT = dspInstInfo->getSAT();
            mv->DSZ = dspInstInfo->getDSZ();
            mv->p = dspInstInfo->getP();
            fixUpRegOnCompactInstruction();
            mv->instFormat = InstFormat::Kind1;
        } else if (checkBits<2>(data, 1, 0b11) && checkBits<2>(data, 5, 0b10)) {
            mv = std::make_shared<MV>(dspInstInfo->getAddress(), baseCycle);
            mv->isCompactInstruction = true;
            mv->compactInstKind = "LSDmvfr";
            mv->dst = extractBits<3>(data, 13).to_ulong();
            mv->dst += extractBits<2>(data, 10).to_ulong() << 3;
            mv->x = data[12];
            mv->src2 = extractBits<3>(data, 7).to_ulong();
            mv->s = data[0];
            auto unit = extractBits<2>(data, 3);
            if (unit == 0b00)
                if (mv->s == 0)
                    mv->funcUnit = FuncUnit::L1;
                else
                    mv->funcUnit = FuncUnit::L2;
            else if (unit == 0b01) {
                if (mv->s == 0) {
                    mv->funcUnit = FuncUnit::S1;
                } else {
                    mv->funcUnit = FuncUnit::S2;
                }
            } else if (unit == 0b10) {
                if (mv->s == 0) {
                    mv->funcUnit = FuncUnit::D1;
                } else {
                    mv->funcUnit = FuncUnit::D2;
                }
            }
            mv->PROT = dspInstInfo->getPROT();
            mv->RS = dspInstInfo->getRS();
            mv->BR = dspInstInfo->getBR();
            mv->DSZ = dspInstInfo->getDSZ();
            mv->SAT = dspInstInfo->getSAT();
            mv->p = dspInstInfo->getP();
            mv->fixUpRegOnCompactInstruction();
            mv->instFormat = InstFormat::Kind1;
        }
    }
    return mv;
}

void MV::executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) {

}

std::string MV::toString() const {
    std::string res;
    if (!isCompactInstruction) {
        res += getStandardConditionString() + " ";
    }
    res += m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " ";
    switch (instFormat) {
        case InstFormat::Kind1:
            res += getSrc2Reg32Name() + "," + getDstReg32Name();
            break;
        case InstFormat::Kind2:
            res += getSrc2Reg64Name() + "," + getDstReg64Name();
            break;
        default:
            break;
    }
    return res;
}

std::shared_ptr<DspInstruction> MV::clone() {
    return std::make_shared<MV>(*this);
}
